package org.tlang.ast.list.func;

import org.tlang.ast.AST;
import org.tlang.ast.ASTList;
import org.tlang.ast.leaf.Name;
import org.tlang.ast.list.type.TypeTag;
import org.tlang.context.SymbolTable;
import org.tlang.context.TypeTable;
import org.tlang.context.ValueTable;
import org.tlang.exception.TypeException;
import org.tlang.keywords.KeyWords;
import org.tlang.metaclass.TFunction;
import org.tlang.metaclass.TMethod;
import org.tlang.metaclass.TReturn;
import org.tlang.type.FuncType;
import org.tlang.type.ReturnType;
import org.tlang.type.Type;

import java.util.List;

public class FunctionDefine extends ASTList implements TFunction {
    private ValueTable outerValueTable;
    private int index;
    private int size;

    public FunctionDefine(List<AST> children) {
        super(children);
    }

    public String name() {
        return ((Name) child(0)).token().getText();
    }

    private ParameterList parameters() {
        return (ParameterList) child(1);
    }

    private TypeTag typeTag() {
        if (numChildren() > 3) {
            return ((TypeTag) child(2));
        } else {
            return null;
        }
    }

    private AST block() {
        return child(numChildren() - 1);
    }

    private Type returnType(TypeTable typeTable) {
        if (numChildren() > 3) {
            return ((TypeTag) child(2)).type(typeTable);
        } else if (KeyWords.CONSTRUCTOR.equals(name())) {
            return typeTable.get(0, 0);
        } else {
            return null;
        }
    }

    public FuncType funcType(TypeTable typeTable) {
        return new FuncType(name(), returnType(typeTable), parameters().types(typeTable));
    }

    public void setOuterValueTable(ValueTable valueTable) {
        this.outerValueTable = valueTable;
    }

    @Override
    public Object execute(ValueTable callerValueTable, ArgumentList arguments) {
        ValueTable localValueTable = new ValueTable(size, this.outerValueTable, "function<" + name() + ">,local");
        parameters().eval(localValueTable); // 向局部环境注入参数
        arguments.eval(localValueTable, callerValueTable, parameters().offsets()); // 向局部环境注入参数值

        // 在局部环境中计算函数结果，计算完成后局部环境销毁
        Object result = block().eval(localValueTable);
        if (result instanceof TReturn) {
            return ((TReturn) result).value();
        }

        return null;
    }

    @Override
    public String toString() {
        return "func " + name() + " " + parameters() + " " + typeTag() + " " + block();
    }

    @Override
    public Object eval(ValueTable valueTable) {
        String name = name();
        setOuterValueTable(valueTable);
        valueTable.add(index, this);
        return name;
    }

    public void evalAsMethod(ValueTable valueTable) {
        TMethod method = new TMethod(1, index, size, block(), parameters(), name());
        valueTable.outer().add(index, method);
    }

    @Override
    public void lookup(SymbolTable symbolTable, TypeTable typeTable) {
        index = symbolTable.add(name());
        lookupTypeTag(symbolTable, typeTable);
        typeTable.put(index, funcType(typeTable));
        lookupInternal(symbolTable, typeTable);
    }

    public void lookupAsMethod(SymbolTable symbolTable, TypeTable typeTable) {
        index = symbolTable.outer().put(name());
        lookupTypeTag(symbolTable, typeTable);
        typeTable.outer().put(index, funcType(typeTable));
        lookupInternal(symbolTable, typeTable);
    }

    private void lookupTypeTag(SymbolTable symbolTable, TypeTable typeTable) {
        TypeTag typeTag = typeTag();
        if (typeTag != null) {
            typeTag.lookup(symbolTable, typeTable);
        }
    }

    private void lookupInternal(SymbolTable symbolTable, TypeTable typeTable) {
        SymbolTable localSymbolTable = new SymbolTable(symbolTable, "function<" + name() + ">,local");
        TypeTable localTypeTable = new TypeTable(1, typeTable, "function<" + name() + ">,local");
        parameters().lookup(localSymbolTable, localTypeTable);
        block().lookup(localSymbolTable, localTypeTable);
        size = localSymbolTable.size();
    }

    @Override
    public Type typeCheck(TypeTable typeTable) throws TypeException {
        Type returnType = returnType(typeTable);
        TypeTable localTypeTable = new TypeTable(size, typeTable, "function<" + name() + ">,local");
        parameters().typeCheck(localTypeTable);
        Type blockType = block().typeCheck(localTypeTable);
        if (blockType instanceof ReturnType) {
            ((ReturnType) blockType).type().assertSubTypeOf(returnType, typeTable, this);
        } else if (!KeyWords.CONSTRUCTOR.equals(name())) {
            blockType.assertSubTypeOf(returnType, typeTable, this);
        }
        return typeTable.get(0, index);
    }
}
